﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UtilZ.Dotnet.Ex.Base;
using UtilZ.Dotnet.Ex.Model;
using UtilZ.Dotnet.Ex.ZLog;

namespace UtilZ.Dotnet.Ex.Communication.Net.Web
{
    /// <summary>
    /// http响应类
    /// </summary>
    public partial class HttpResponse
    {
        /// <summary>
        /// http响应方法
        /// </summary>
        /// <param name="stream">写入响应数据对应的流</param>
        public void Response(Stream stream)
        {
            BufferData bufferData = this._data;
            this.SetCommonHeader(this._headers, bufferData);

            if (bufferData == null)
            {
                this.OnceSendResponse(this._headers, stream, null);
                return;
            }

            List<HttpRequestRange> requestRangeList = this._requestRangeList;
            int chunkSize = this._chunkSize;
            if (requestRangeList == null || requestRangeList.Count == 0)
            {
                //响应-无请求范围
                this.NoRange(stream, bufferData, chunkSize);
            }
            else if (requestRangeList.Count == 1)
            {
                //响应-请求单一范围
                this.SingleRange(stream, bufferData, chunkSize, requestRangeList.First());
            }
            else
            {
                //响应-请求多重范围                    
                this.MuiltRange(stream, bufferData, chunkSize, requestRangeList);
            }
        }

        private void MuiltRange(Stream stream, BufferData bufferData, int chunkSize, List<HttpRequestRange> requestRangeList)
        {
            switch (bufferData.DataType)
            {
                case BufferDataType.Bytes:
                    using (var ms = new MemoryStream(bufferData.Bytes))
                    {
                        this.PrimitiveMuiltRange(stream, ms, chunkSize, requestRangeList);
                    }
                    break;
                case BufferDataType.Stream:
                    this.PrimitiveMuiltRange(stream, bufferData.Stream, chunkSize, requestRangeList);
                    break;
                case BufferDataType.File:
                    using (var fs = new FileStream(bufferData.FilePath, FileMode.Open, FileAccess.Read, FileShare.Read))
                    {
                        this.PrimitiveMuiltRange(stream, fs, chunkSize, requestRangeList);
                    }
                    break;
                default:
                    throw new NotImplementedException(bufferData.DataType.ToString());
            }
        }

        private void PrimitiveMuiltRange(Stream stream, Stream dataStream, int chunkSize, List<HttpRequestRange> requestRangeList)
        {
            /********************************************************************************************************************************************
             * Range 头中还可以列出多个范围，之间以逗号进行分隔，如下所示：
             * GET /golang/ HTTP/1.1
             * Host: c.biancheng.net
             * User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.125 Safari/537.36
             * Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,* /*;q=0.8,application/signed-exchange;v=b3;q=0.9
             * Referer: http://c.biancheng.net/sitemap/
             * Range: bytes=0-50, 100-150
             * 
             * 服务器响应如下所示：
             * HTTP/1.1 206 Partial Content\r\n
             * Content-Type: multipart/byteranges; boundary=3d6b6a416f9b5\r\n
             * Content-Length: 282\r\n
             * \r\n
             * --3d6b6a416f9b5\r\n
             * Content-Type: text/html\r\n
             * Content-Range: bytes 0-50/1270\r\n
             * \r\n
             * 内容\r\n
             * --3d6b6a416f9b5\r\n
             * Content-Type: text/html\r\n
             * Content-Range: bytes 100-150/1270\r\n
             * \r\n
             * 内容\r\n
             * --3d6b6a416f9b5--
             * 
             * 注:
             * 上面的响应中，Content-Type：multipart/byteranges表示这个响应有多个 byterange。
             * 每一部分 byterange 都有他自己的 Centen-type 头和 Content-Range 头，并且使用 boundary 参数对响应体进行划分。
             * 
             * 与范围请求相关的有三种状态码：
             * 若请求成功，服务器会返回  206 状态码；
             * 若请求的范围越界（范围值超过了资源的大小），服务器会返回 416 状态码；
             * 若服务器不支持范围请求，服务器会返回 200 状态码。
             ********************************************************************************************************************************************/


            string contentType;//内容类型,每项范围中都会使用
            if (!this._headers.TryGetValue(HttpResponseHeaders.ContentType, out contentType))
            {
                //如果未设置,则默认使用二进制
                contentType = HttpContentTypes.STREAM;
            }

            string boundary = this.GenerateMuiltRangeBoundary();
            this.AddHeader(HttpResponseHeaders.ContentType, $"{HttpContentTypes.MULTIPART_BYTERANGES};boundary={boundary}");


            var beginBoundaryItem = new HeaderItem($"--{boundary}");//起始及内容分隔边界
            var endBoundary = $"\r\n--{boundary}--";//内容结束边界
            var contentTypeItem = new HeaderItem(HttpResponseHeaders.ContentType, contentType);

            var contentRanges = new List<ContentRangeInfo>();
            long contentLength = 0L;
            ContentRangeInfo contentRangeInfo;
            foreach (HttpRequestRange requestRange in requestRangeList)
            {
                contentRangeInfo = requestRange.GetArea(dataStream.Length);
                contentLength += contentRangeInfo.Length;
                contentRanges.Add(contentRangeInfo);
            }

            Stream contentStream = null;
            string filePath = null;
            try
            {
                if (contentLength <= WebConstnats.RES_PART_CONTENT_MAX_SIZE)
                {
                    //响应数据在内存中拼接
                    contentStream = new MemoryStream();
                }
                else
                {
                    //响应数据在文件中拼接
                    filePath = Path.Combine("resTemp", $"{Guid.NewGuid().ToString()}.dat");
                    DirectoryInfoEx.CheckFilePathDirectory(filePath);
                    contentStream = new FileStream(filePath, FileMode.Create, FileAccess.ReadWrite);
                }

                byte[] buffer = new byte[NetHelper.MTU_MAX];
                var headers = new List<HeaderItem>();
                byte[] contentRangeHeader;
                int readLength;
                long readMod, dataStreamEndPostion;

                //构造多重范围内容数据
                foreach (ContentRangeInfo contentRange in contentRanges)
                {
                    //范围头
                    headers.Clear();
                    headers.Add(beginBoundaryItem);
                    headers.Add(contentTypeItem);
                    headers.Add(new HeaderItem(HttpResponseHeaders.ContentLength, contentRange.ContentRange));
                    contentRangeHeader = this.BuildContentRangeHeader(headers);
                    contentStream.Write(contentRangeHeader, 0, contentRangeHeader.Length);
                    this._connection.UpdateLastRequestTime();

                    //内容
                    dataStream.Seek(contentRange.Offset, SeekOrigin.Begin);
                    dataStreamEndPostion = contentRange.Offset + contentRange.Length;
                    readLength = buffer.Length;
                    while (dataStream.Position < dataStreamEndPostion)
                    {
                        readMod = dataStreamEndPostion - dataStream.Position;
                        if (readMod < readLength)
                        {
                            readLength = (int)readMod;
                        }

                        readLength = dataStream.Read(buffer, 0, readLength);
                        contentStream.Write(buffer, 0, readLength);
                        contentStream.Flush();
                        this._connection.UpdateLastRequestTime();
                    }
                }
                //写内容结束边界
                byte[] endBoundaryBuffer = Encoding.ASCII.GetBytes(endBoundary);
                contentStream.Write(endBoundaryBuffer, 0, endBoundaryBuffer.Length);
                contentStream.Flush();

                //构造http响应头中的内容大小
                long dataLength = contentStream.Position;
                this.AddHeader(HttpResponseHeaders.ContentLength, dataLength.ToString());

                //发送响应头
                string header = this.BuildHeader(this._headers);
                byte[] headerBytes = Encoding.ASCII.GetBytes(header);
                stream.Write(headerBytes, 0, headerBytes.Length);
                this._connection.UpdateLastRequestTime();

                //发送响应内容数据
                contentStream.Position = 0L;
                readLength = buffer.Length;
                while (contentStream.Position < dataLength)
                {
                    readMod = dataLength - contentStream.Position;
                    if (readMod < readLength)
                    {
                        readLength = (int)readMod;
                    }

                    readLength = contentStream.Read(buffer, 0, readLength);
                    stream.Write(buffer, 0, readLength);
                    stream.Flush();
                    this._connection.UpdateLastRequestTime();
                }
            }
            catch
            {
                if (contentStream != null)
                {
                    contentStream.Close();
                    contentStream.Dispose();
                }

                if (filePath != null)
                {
                    FileEx.TryDeleFile(filePath);
                }

                throw;
            }
        }

        /// <summary>
        /// 构建内容范围响应头部
        /// </summary>
        /// <returns></returns>
        private byte[] BuildContentRangeHeader(List<HeaderItem> headers)
        {
            StringBuilder builder = new StringBuilder();
            foreach (var headItem in headers)
            {
                if (headItem.ItemType)
                {
                    builder.Append(headItem.Name);
                    builder.Append(WebConstnats.COLON_MARK_ARR);
                    builder.Append(headItem.Value);
                }
                else
                {
                    builder.Append(headItem.Header);
                }
                builder.Append(WebConstnats.ENTER_NEWLINE);
            }
            builder.Append(WebConstnats.ENTER_NEWLINE);
            return Encoding.ASCII.GetBytes(builder.ToString());
        }

        private string GenerateMuiltRangeBoundary()
        {
            string str = new Guid().ToString() + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
            byte[] buffer = Encoding.UTF8.GetBytes(str);
            var sha1 = System.Security.Cryptography.SHA1.Create();
            byte[] hashBuffer = sha1.ComputeHash(buffer);
            return Convert.ToBase64String(hashBuffer);
        }







        private void SingleRange(Stream stream, BufferData bufferData, int chunkSize, HttpRequestRange requestRange)
        {
            /********************************************************************************************************************************************
            * 单范围请求
            * GET /templets/new/images/logo.png HTTP/1.1
            * Host: c.biancheng.net
            * Connection: keep-alive
            * User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.125 Safari/537.36
            * Accept: image/webp,image/apng,image/*,* /*;q=0.8
            * Referer: http://c.biancheng.net/golang/
            * Range: bytes=0-1023
            * 
            * 服务器响应如下所示：
            * HTTP/1.1 206 Partial Content\r\n
            * Content-Range: bytes 0-1023/146515\r\n
            * Content-Length: 1024\r\n
            * \r\n
            * ...
            ********************************************************************************************************************************************/


            ContentRangeInfo contentRangeInfo;
            switch (bufferData.DataType)
            {
                case BufferDataType.Bytes:
                    using (var ms = new MemoryStream(bufferData.Bytes))
                    {
                        contentRangeInfo = requestRange.GetArea(ms.Length);
                        this.AddHeader(HttpResponseHeaders.ContentRange, contentRangeInfo.ContentRange);

                        if (contentRangeInfo.Length > chunkSize)
                        {
                            this.ChunkSendResponseStream(this._headers, stream, chunkSize, ms, contentRangeInfo.Offset, contentRangeInfo.Length);
                        }
                        else
                        {
                            byte[] buffer = bufferData.Bytes.Skip((int)contentRangeInfo.Offset).Take((int)contentRangeInfo.Length).ToArray();
                            this.OnceSendResponse(this._headers, stream, buffer);
                        }
                    }
                    break;
                case BufferDataType.Stream:
                    contentRangeInfo = requestRange.GetArea(stream.Length);
                    this.AddHeader(HttpResponseHeaders.ContentRange, contentRangeInfo.ContentRange);

                    if (contentRangeInfo.Length > chunkSize)
                    {
                        this.ChunkSendResponseStream(this._headers, stream, chunkSize, bufferData.Stream, contentRangeInfo.Offset, contentRangeInfo.Length);
                    }
                    else
                    {
                        byte[] buffer = new byte[contentRangeInfo.Length];
                        bufferData.Stream.Seek(contentRangeInfo.Offset, SeekOrigin.Begin);
                        bufferData.Stream.Read(buffer, 0, buffer.Length);
                        this.OnceSendResponse(this._headers, stream, buffer);
                    }
                    break;
                case BufferDataType.File:
                    using (var fs = new FileStream(bufferData.FilePath, FileMode.Open, FileAccess.Read, FileShare.Read))
                    {
                        contentRangeInfo = requestRange.GetArea(fs.Length);
                        this.AddHeader(HttpResponseHeaders.ContentRange, contentRangeInfo.ContentRange);

                        if (contentRangeInfo.Length > chunkSize)
                        {
                            this.ChunkSendResponseStream(this._headers, stream, chunkSize, fs, contentRangeInfo.Offset, contentRangeInfo.Length);
                        }
                        else
                        {
                            byte[] buffer = new byte[contentRangeInfo.Length];
                            fs.Seek(contentRangeInfo.Offset, SeekOrigin.Begin);
                            fs.Read(buffer, 0, buffer.Length);
                            this.OnceSendResponse(this._headers, stream, buffer);
                        }
                    }
                    break;
                default:
                    throw new NotImplementedException(bufferData.DataType.ToString());
            }
        }

        private void NoRange(Stream stream, BufferData bufferData, int chunkSize)
        {
            switch (bufferData.DataType)
            {
                case BufferDataType.Bytes:
                    using (var ms = new MemoryStream(bufferData.Bytes))
                    {
                        if (bufferData.Bytes.Length > chunkSize)
                        {
                            this.ChunkSendResponseStream(this._headers, stream, chunkSize, ms, bufferData.Offset, bufferData.Length);
                        }
                        else
                        {
                            this.OnceSendResponse(this._headers, stream, bufferData.Bytes);
                        }
                    }
                    break;
                case BufferDataType.Stream:
                    this.ChunkSendResponseStream(this._headers, stream, chunkSize, bufferData.Stream, bufferData.Offset, bufferData.Length);
                    break;
                case BufferDataType.File:
                    using (var fs = new FileStream(bufferData.FilePath, FileMode.Open, FileAccess.Read, FileShare.Read))
                    {
                        bufferData.UpdateFileBufferLength(fs.Length);

                        if (fs.Length > chunkSize)
                        {
                            this.ChunkSendResponseStream(this._headers, stream, chunkSize, fs, bufferData.Offset, bufferData.Length);
                        }
                        else
                        {
                            byte[] content = new byte[fs.Length];
                            fs.Read(content, 0, content.Length);
                            this.OnceSendResponse(this._headers, stream, content);
                        }
                    }
                    break;
                default:
                    throw new NotImplementedException(bufferData.DataType.ToString());
            }
        }





        /// <summary>
        /// 分片发送响应数据
        /// </summary>
        /// <param name="headers"></param>
        /// <param name="stream"></param>
        /// <param name="chunkSize"></param>
        /// <param name="dataStream"></param>
        /// <param name="offset"></param>
        /// <param name="contentLength"></param>
        private void ChunkSendResponseStream(Dictionary<string, string> headers, Stream stream, int chunkSize, Stream dataStream, long offset, long contentLength)
        {
            headers.Remove(HttpResponseHeaders.ContentLength);
            dataStream.Seek(offset, SeekOrigin.Begin);

            //分块发送第一块
            headers[HttpResponseHeaders.TransferEncoding] = WebConstnats.CHUNKED;
            string header = this.BuildHeader(headers);//发送响应头
            byte[] headerBytes = Encoding.ASCII.GetBytes(header);
            stream.Write(headerBytes, 0, headerBytes.Length);
            this._connection.UpdateLastRequestTime();

            long count = dataStream.Length / chunkSize;
            if (contentLength % chunkSize > 0)
            {
                count++;
            }

            long writeOffset = 0, mod;
            int writeLen = chunkSize;
            byte[] buffer = new byte[chunkSize + 12];
            byte[] content = new byte[chunkSize];

            using (var ms = new MemoryStream(buffer))
            {
                var bw = new BinaryWriter(ms);

                //分块发送
                for (var i = 0; i < count; i++)
                {
                    ms.Position = 0;
                    mod = contentLength - writeOffset;
                    if (mod < chunkSize)
                    {
                        writeLen = (int)mod;
                    }

                    writeLen = dataStream.Read(content, 0, writeLen);

                    //chunk大小必须要先转换为16进制字符串,再ASCII编码后发送
                    bw.Write(Encoding.ASCII.GetBytes(Convert.ToString(writeLen, 16)));
                    bw.Write((byte)13);
                    bw.Write((byte)10);

                    //写内容
                    bw.Write(content, 0, writeLen);
                    bw.Write((byte)13);
                    bw.Write((byte)10);

                    bw.Flush();

                    //发送
                    stream.Write(buffer, 0, (int)ms.Position);
                    stream.Flush();
                    this._connection.UpdateLastRequestTime();

                    writeOffset += writeLen;
                }

                //发送结束
                ms.Position = 0;
                bw.Write(Encoding.ASCII.GetBytes(Convert.ToString(0, 16)));
                bw.Write((byte)13);
                bw.Write((byte)10);
                bw.Write((byte)13);
                bw.Write((byte)10);
                stream.Write(buffer, 0, (int)ms.Position);
                stream.Flush();
                this._connection.UpdateLastRequestTime();
            }
        }


        /// <summary>
        /// 一次性发送全部响应数据
        /// </summary>
        /// <param name="headers"></param>
        /// <param name="stream"></param>
        /// <param name="content"></param>
        private void OnceSendResponse(Dictionary<string, string> headers, Stream stream, byte[] content)
        {
            if (!stream.CanWrite)
            {
                ZLoger.Warn("http响应流不可写");
                return;
            }

            if (!headers.ContainsKey(HttpResponseHeaders.ContentLength))
            {
                int contentLength = 0;
                if (content != null)
                {
                    contentLength = content.Length;
                }

                headers[HttpResponseHeaders.ContentLength] = contentLength.ToString();
            }

            string header = this.BuildHeader(headers);//发送响应头
            byte[] headerBytes = Encoding.ASCII.GetBytes(header);
            stream.Write(headerBytes, 0, headerBytes.Length);

            if (content != null)
            {
                stream.Write(content, 0, content.Length);
            }

            stream.Flush();
            this._connection.UpdateLastRequestTime();
        }

        /// <summary>
        /// 构建响应头部
        /// </summary>
        /// <returns></returns>
        private string BuildHeader(Dictionary<string, string> headers)
        {
            StringBuilder builder = new StringBuilder();

            // HTTP/1.1 200 OK
            builder.Append(this.HttpProtocolVer);
            builder.Append(WebConstnats.ONE_SPACE_ARR);
            builder.Append(this.StatusCode);
            builder.Append(WebConstnats.ONE_SPACE_ARR);
            builder.Append(HttpStatusCodes.GetStatusCodeName(this.StatusCode));
            builder.Append(WebConstnats.ENTER_NEWLINE);

            foreach (var kv in headers)
            {
                builder.Append(kv.Key);
                builder.Append(WebConstnats.COLON_MARK_ARR);
                builder.Append(kv.Value);
                builder.Append(WebConstnats.ENTER_NEWLINE);
            }

            if (this._extendHeaders != null)
            {
                foreach (var headItem in this._extendHeaders)
                {
                    if (headItem.ItemType)
                    {
                        builder.Append(headItem.Name);
                        builder.Append(WebConstnats.COLON_MARK_ARR);
                        builder.Append(headItem.Value);
                    }
                    else
                    {
                        builder.Append(headItem.Header);
                    }
                    builder.Append(WebConstnats.ENTER_NEWLINE);
                }
            }

            builder.Append(WebConstnats.ENTER_NEWLINE);

            return builder.ToString();
        }



        /// <summary>
        /// 设置公共头
        /// </summary>
        /// <param name="headers">头字典集合</param>
        /// <param name="bufferData">缓存数据</param>
        private void SetCommonHeader(Dictionary<string, string> headers, BufferData bufferData)
        {
            headers[HttpResponseHeaders.AccessControlAllowOrigin] = "*";
            headers[HttpResponseHeaders.Date] = DateTime.Now.ToString("r");
            headers[HttpResponseHeaders.Server] = "UtilZWebServer";

            if (!headers.ContainsKey(HttpResponseHeaders.ContentType) &&
                bufferData != null &&
                bufferData.DataType == BufferDataType.File)
            {
                string extension = Path.GetExtension(bufferData.FilePath);
                headers[HttpResponseHeaders.ContentType] = HttpContentTypes.GetMimeMapping(extension);
            }
        }




        /// <summary>
        /// 处理多重范围
        /// </summary>
        /// <param name="contentType"></param>
        /// <param name="httpResponse"></param>
        /// <param name="fs"></param>
        /// <param name="reqRangeList"></param>
        private void ProcessmuiltRangeRequest(string contentType, HttpResponse httpResponse, FileStream fs, List<HttpRequestRange> reqRangeList)
        {
            //string boundary = this.GenerateBoundary();
            //httpResponse.AddHeader(HttpResponseHeaders.ContentType, $"{HttpContentTypes.MULTIPART_BYTERANGES};boundary={boundary}");

            //var contentTypeItem = new HeaderItem(HttpResponseHeaders.ContentType, contentType);
            //var boundaryItem = new HeaderItem($"--{boundary}");
            //var newLineItem = new HeaderItem(WebConstnats.ENTER_NEWLINE);
            //BufferData bufferData;
            //string contentRange;

            //using (var ms = new MemoryStream())
            //{
            //    foreach (var reqRange in reqRangeList)
            //    {
            //        bufferData = reqRange.GetBufferData(fs, out contentRange);
            //        ms.Write(buffer, 0, buffer.Length);
            //        ms.Flush();
                      //this._connection.UpdateLastRequestTime();
            //        httpResponse.AddExtendHeader(boundaryItem);
            //        httpResponse.AddExtendHeader(contentTypeItem);
            //        httpResponse.AddExtendHeader(new HeaderItem(HttpResponseHeaders.ContentRange, contentRange));
            //        httpResponse.AddExtendHeader(newLineItem);
            //    }

            //    httpResponse.Content = ms.GetBuffer().Take((int)ms.Position).ToArray();
            //    httpResponse.AddExtendHeader(new HeaderItem($"--{boundary}--"));
            //}
        }


    }
}
